import { useEffect, useRef, useMemo, useState } from "react";
import { cloneDeep, get, merge } from "lodash-es";
import type { PaginationProps } from "../types/pagination";
import type { BasicTableProps, FetchParams, SorterResult } from "../types/table";
import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from "../const";
import { isBoolean, isFunction, isObject } from "@/utils/is";
import { buildUUID } from "@/utils/util";
let dataSource: any;

interface ActionType {
	getPaginationInfo: boolean | PaginationProps;
	setPagination: (info: Partial<PaginationProps>) => void;
	setLoading: (loading: boolean) => void;
	getFieldsValue: () => Recordable;
	clearSelectedRowKeys: () => void;
	setTableDataRef: Fn;
}
interface SearchState {
	sortInfo: Recordable;
	filterInfo: Record<string, string[]>;
}
export function useDataSource(
	propsRef: BasicTableProps,
	{ getPaginationInfo, setPagination, setLoading, getFieldsValue, clearSelectedRowKeys, setTableDataRef }: ActionType
) {
	const searchState = useRef<SearchState>({
		sortInfo: {},
		filterInfo: {}
	});
	const [dataSourceRef, setDataSourceRef] = useState<Recordable[]>([]);
	const rawDataSourceRef = useRef<Recordable>({});
	const [paramRef, setParamRef] = useState({});

	useEffect(() => {
		const { dataSource, api } = propsRef;
		!api && dataSource ? setDataSourceRef(dataSource) : propsRef.immediate && fetch();
	}, [propsRef.api, propsRef.dataSource]);

	// React states are asynchronous updates
	useEffect(() => {
		fetch(paramRef);
	}, [paramRef]);

	useEffect(() => {
		dataSource = cloneDeep(dataSourceRef);
		setTableDataRef(dataSourceRef);
	}, [dataSourceRef]);

	function handleTableChange(pagination: PaginationProps, filters: Partial<Recordable<string[]>>, sorter: SorterResult) {
		const { clearSelectOnPageChange, sortFn, filterFn } = propsRef;
		if (clearSelectOnPageChange) clearSelectedRowKeys();
		setPagination(pagination);
		const params: Recordable = {};
		if (sorter && isFunction(sortFn)) {
			const sortInfo = sortFn(sorter);
			searchState.sortInfo = sortInfo;
			params.sortInfo = sortInfo;
		}
		if (filters && isFunction(filterFn)) {
			const filterInfo = filterFn(filters);
			searchState.filterInfo = filterInfo;
			params.filterInfo = filterInfo;
		}
		setParamRef({
			...params
		});
	}

	function setTableKey(items: any[]) {
		if (!items || !Array.isArray(items)) return;
		items.forEach(item => {
			if (!item[ROW_KEY]) item[ROW_KEY] = buildUUID();

			if (item.children && item.children.length) setTableKey(item.children);
		});
	}

	const getAutoCreateKey = useMemo(() => propsRef.autoCreateKey && !propsRef.rowKey, [propsRef.autoCreateKey, propsRef.rowKey]);

	const getRowKey = useMemo(() => {
		const { rowKey } = propsRef;
		return getAutoCreateKey ? ROW_KEY : rowKey;
	}, [propsRef, getAutoCreateKey]);

	const getDataSourceRef = useMemo(() => {
		const dataSource = cloneDeep(dataSourceRef);
		if (!dataSource || dataSource.length === 0) return dataSource;

		if (getAutoCreateKey) {
			const firstItem = dataSource[0];
			const lastItem = dataSource[dataSource.length - 1];
			if (firstItem && lastItem && (!firstItem[ROW_KEY] || !lastItem[ROW_KEY])) {
				dataSource.forEach(item => {
					if (!item[ROW_KEY]) item[ROW_KEY] = buildUUID();
					if (item.children && item.children.length) setTableKey(item.children);
				});
				setDataSourceRef(dataSource);
			}
		}
		return dataSource;
	}, [dataSourceRef, getAutoCreateKey]);

	async function updateTableData(index: number, key: string, value: any) {
		const record = dataSourceRef[index];
		if (record) dataSourceRef[index][key] = value;

		return dataSourceRef[index];
	}

	function updateTableDataRecord(rowKey: string | number, record: Recordable) {
		setDataSourceRef(preVal => {
			const index = findTableDataRecordIndex(rowKey, preVal);
			return preVal.map((item, idx) => (idx === index ? record : item));
		});
	}

	function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
		if (!dataSource || dataSource.length === 0) return;
		const rowKeyName = getRowKey;
		if (!rowKeyName) return;
		const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
		function deleteRow(data, key) {
			const row: { index: number; data: [] } = findRow(data, key);
			if (row === null || row.index === -1) return;
			row.data.splice(row.index, 1);
			dataSource = row.data;
			setDataSourceRef(dataSource);
			function findRow(data, key) {
				if (data === null || data === undefined) return null;

				for (let i = 0; i < data.length; i++) {
					const row = data[i];
					let targetKeyName: string = rowKeyName as string;
					if (isFunction(rowKeyName)) targetKeyName = rowKeyName(row);

					if (row[targetKeyName] === key) return { index: i, data };

					if (row.children?.length > 0) {
						const result = findRow(row.children, key);
						if (result != null) return result;
					}
				}
				return null;
			}
		}

		for (const key of rowKeys) {
			deleteRow(dataSource, key);
			// deleteRow(propsRef.dataSource, key);
		}
		setPagination({
			total: propsRef.dataSource?.length
		});
	}
	function insertTableDataRecord(record: Recordable | Recordable[], index?: number): Recordable[] | undefined {
		index = index ?? (dataSourceRef?.length as number);
		const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]);
		const data = cloneDeep(dataSource);
		data.splice(index, 0, ..._record);
		setDataSourceRef(data);
		return dataSourceRef;
	}
	function findTableDataRecordIndex(rowKey: string | number, records: Recordable[]): number | null {
		if (!records || records.length === 0) return null;
		const rowKeyName = getRowKey;
		if (!rowKeyName) return null;
		const { childrenColumnName = "children" } = propsRef;
		const findRow = (array: any[]) => {
			let index = null;
			array.some(function iter(r, idx) {
				if (typeof rowKeyName === "function") {
					if ((rowKeyName(r) as string) === rowKey) {
						index = idx;
						return true;
					}
				} else {
					if (Reflect.has(r, rowKeyName as string) && r[rowKeyName as string] === rowKey) {
						index = idx;
						return true;
					}
				}
				return r[childrenColumnName] && r[childrenColumnName].some(iter);
			});
			return index;
		};
		return findRow(records);
	}

	async function fetch(opt?: FetchParams) {
		const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = propsRef;
		if (!api || !isFunction(api)) return;
		try {
			setLoading(true);
			const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting);
			let pageParams: Recordable = {};

			const { current = 1, pageSize = PAGE_SIZE } = getPaginationInfo as PaginationProps;

			if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
				pageParams = {};
			} else {
				pageParams[pageField] = (opt && opt.page) || current;
				pageParams[sizeField] = pageSize;
			}

			const { sortInfo = {}, filterInfo } = searchState;

			let params: Recordable = merge(
				pageParams,
				useSearchForm ? getFieldsValue() : {},
				searchInfo,
				opt?.searchInfo ?? {},
				defSort,
				sortInfo,
				filterInfo,
				opt?.sortInfo ?? {},
				opt?.filterInfo ?? {}
			);

			if (beforeFetch && isFunction(beforeFetch)) params = (await beforeFetch(params)) || params;

			const res = await api(params);
			rawDataSourceRef.current = res;

			const isArrayResult = Array.isArray(res);
			let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
			const resultTotal: number = isArrayResult ? res.length : get(res, totalField);

			// 假如数据变少，导致总页数变少并小于当前选中页码，通过getPaginationRef获取到的页码是不正确的，需获取正确的页码再次执行
			if (Number(resultTotal)) {
				const currentTotalPage = Math.ceil(resultTotal / pageSize);
				if (current > currentTotalPage) {
					setPagination({
						current: currentTotalPage
					});
					return await fetch(opt);
				}
			}

			if (afterFetch && isFunction(afterFetch)) resultItems = (await afterFetch(resultItems)) || resultItems;

			setDataSourceRef(resultItems);
			setPagination({
				total: resultTotal || 0
			});
			if (opt && opt.page) {
				setPagination({
					current: opt.page || 1
				});
			}
		} catch (error) {
			setDataSourceRef([]);
			setPagination({
				total: 0
			});
		} finally {
			setLoading(false);
		}
	}

	function setTableData<T = Recordable>(values: T[]) {
		setDataSourceRef(values as Recordable[]);
	}

	function getDataSource<T = Recordable>() {
		return getDataSourceRef as T[];
	}

	function getRawDataSource<T = Recordable>() {
		return rawDataSourceRef.current as T;
	}

	function reload(opt?: FetchParams) {
		setParamRef({
			...paramRef,
			...opt
		});
	}

	return {
		getDataSourceRef,
		getDataSource,
		getRawDataSource,
		getRowKey,
		setTableData,
		getAutoCreateKey,
		fetch,
		reload,
		updateTableData,
		updateTableDataRecord,
		deleteTableDataRecord,
		insertTableDataRecord,
		findTableDataRecordIndex,
		handleTableChange
	};
}
